<template>
  <div class="center-map-box" id="contant"></div>
</template>

<script setup>
import { onMounted, onUnmounted, ref, watch } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import {
  CSS2DRenderer,
  CSS2DObject,
} from "three/addons/renderers/CSS2DRenderer.js";
import * as d3 from "d3";

let textureLoader = new THREE.TextureLoader(); //纹理贴图加载器
let WaveMeshArr = []; //所有波动光圈集合
let rotatingApertureMesh, rotatingPointMesh;
// 墨卡托投影转换
const projection = d3.geoMercator();

let scene, camera, renderer, labelRenderer, raycaster;
let spotLight, map, contant;
let centerPos = [114.255221, 30.619014]; // 地图中心经纬度坐标
let mouse = new THREE.Vector2(0, 0); // 鼠标位置
// 在setup中声明响应式变量
const checkMesh = ref([]);

// 组件挂载时初始化
onMounted(() => {
  // 第一步新建一个场景
  scene = new THREE.Scene();
  // 辅助线
  const axesHelper = new THREE.AxesHelper(10);
  scene.add(axesHelper);

  //环境光
  const ambient = new THREE.AmbientLight("#ffffff", 4);
  scene.add(ambient);
  contant = document.getElementById("contant");

  setCamera();
  setRenderer();
  generateGeometry();
  setClickFn();
  setController();
  animate();
  window.onresize = () => {
    renderer.setSize(contant.clientWidth, contant.clientHeight);
    camera.aspect = contant.clientWidth / contant.clientHeight;
    camera.updateProjectionMatrix();
  };

  initFloor();
});

onUnmounted(() => {
  renderer.value.dispose(); // 清理渲染器
});

// watch监听
watch(
  checkMesh,
  (newVal) => {
    changeMesh(newVal);
  },
  { deep: true }
);
// 新建透视相机
function setCamera() {
  camera = new THREE.PerspectiveCamera(
    45,
    contant.clientWidth / contant.clientHeight,
    0.1,
    1000
  );
  // 设置相机位置
  camera.position.set(20, 20, 20);
}

// 设置渲染器
function setRenderer() {
  renderer = new THREE.WebGLRenderer();
  // 设置画布的大小
  renderer.setSize(contant.clientWidth, contant.clientHeight);
  contant.appendChild(renderer.domElement);

  // 创建CSS2DRenderer渲染器(代替鼠标射线检测)
  labelRenderer = new CSS2DRenderer();
  // 设置labelRenderer渲染器宽高
  labelRenderer.setSize(contant.clientWidth, contant.clientHeight);
  labelRenderer.domElement.style.position = "absolute";
  labelRenderer.domElement.style.top = "0px";
  labelRenderer.domElement.style.pointerEvents = "none";
  // 将渲染器添加到页面
  contant.appendChild(labelRenderer.domElement);
}

function render() {
  renderer.render(scene, camera);

  labelRenderer.render(scene, camera);
}

// 绘制地图
function generateGeometry() {
  // 初始化一个地图对象
  map = new THREE.Object3D();
  // 地理坐标数据 转换为3D坐标数据
  // 墨卡托投影转换
  projection.center(centerPos).scale(200).translate([0, 0]);
  const url = "https://geo.datav.aliyun.com/areas_v3/bound/420000_full.json";
  fetch(url)
    .then((res) => res.json())
    .then((jsondata) => {
      jsondata.features.forEach((elem) => {
        const coordinates = elem.geometry.coordinates;
        const province = new THREE.Object3D();
        province.name = elem.properties.name;
        // 拉伸 地图厚度
        const depth = 1;
        //这里创建光柱、文字坐标
        const lightPoint = initLightPoint(elem.properties, depth);
        // 循环坐标数组
        coordinates.forEach((multiPolygon) => {
          multiPolygon.forEach((polygon) => {
            // 创建地图区块
            const mesh = createMesh(polygon, depth);
            // 创建地图区块边界线
            const line = createLine(polygon, depth);
            province.add(mesh, line, ...lightPoint);
          });
        });
        // 地图区块添加到地图对象中
        map.add(province);
      });

      // 地图放到中心位置
      setCenter(map);
      // 地图添加到场景中
      scene.add(map);
      // 场景渲染
      render();
    });
}

/**
 * @description // 创建光柱
 * @param {*} x d3 - 经纬度转换后的x轴坐标
 * @param {*} y d3 - 经纬度转换后的z轴坐标
 * @param {*} heightScaleFactor
 */
function createLightPillar(x, y, heightScaleFactor = 1, depth) {
  let group = new THREE.Group();
  // 柱体高度
  const height = heightScaleFactor;
  // 柱体的geo,6.19=柱体图片高度/宽度的倍数
  const geometry = new THREE.PlaneGeometry(height / 6.219, height);
  // 柱体旋转90度，垂直于Y轴
  geometry.rotateX(Math.PI / 2);
  // 柱体的z轴移动高度一半对齐中心点
  geometry.translate(0, 0, height / 2);
  // 柱子材质
  const material = new THREE.MeshBasicMaterial({
    map: textureLoader.load(
      new URL("../assets/mapimg/光柱.png", import.meta.url).href
    ),
    color: 0x00ffff,
    transparent: true,
    depthWrite: false,
    // depthTest:false,
    side: THREE.DoubleSide,
  });
  // 光柱01
  let light01 = new THREE.Mesh(geometry, material);
  light01.renderOrder = 2;
  light01.name = "createLightPillar01";
  // 光柱02：复制光柱01
  let light02 = light01.clone();
  light02.renderOrder = 2;
  light02.name = "createLightPillar02";
  // 光柱02，旋转90°，跟 光柱01交叉
  light02.rotateZ(Math.PI / 2);
  // 创建底部标点
  const bottomMesh = createPointMesh(0.5);
  // 创建光圈
  const lightHalo = createLightHalo(0.5);
  WaveMeshArr.push(lightHalo);
  // 将光柱和标点添加到组里
  group.add(bottomMesh, lightHalo, light01, light02);
  group.position.set(x, -y, depth + 0.01);
  return group;
}

/**
 * @description 创建底部标点
 * @param {number} size 缩放大小
 */
function createPointMesh(size) {
  // 标记点：几何体，材质，
  const geometry = new THREE.PlaneGeometry(1, 1);
  const material = new THREE.MeshBasicMaterial({
    map: textureLoader.load(
      new URL("../assets/mapimg/标注.png", import.meta.url).href
    ),
    color: 0x00ffff,
    side: THREE.DoubleSide,
    transparent: true,
    depthWrite: false, //禁止写入深度缓冲区数据
  });
  let mesh = new THREE.Mesh(geometry, material);
  mesh.renderOrder = 2;
  mesh.name = "createPointMesh";
  // 缩放
  const scale = 1 * size;
  mesh.scale.set(scale, scale, scale);
  return mesh;
}

/**
 * @description 创建底部标点的光圈
 * @param {number} size 缩放大小
 */
function createLightHalo(size) {
  // 标记点：几何体，材质，
  const geometry = new THREE.PlaneGeometry(1, 1);
  const material = new THREE.MeshBasicMaterial({
    map: textureLoader.load(
      new URL("../assets/mapimg/标注光圈.png", import.meta.url).href
    ),
    color: 0x00ffff,
    side: THREE.DoubleSide,
    opacity: 0,
    transparent: true,
    depthWrite: false, //禁止写入深度缓冲区数据
  });
  let mesh = new THREE.Mesh(geometry, material);
  mesh.renderOrder = 2;
  mesh.name = "createLightHalo";
  // 缩放
  const scale = 1.5 * size;
  mesh.size = scale; //自顶一个属性，表示mesh静态大小
  mesh.scale.set(scale, scale, scale);
  return mesh;
}

function random(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

/**
 * @description 创建光柱、文字坐标
 * @param {*} properties 属性、详情
 * @param {Function} projection  d3-geo转化坐标
 */
function initLightPoint(properties, depth) {
  // 创建光柱
  let heightScaleFactor = 2 + random(1, 5) / 5;
  let lightCenter = properties.centroid || properties.center;
  let areaName = properties.name;
  // projection用来把经纬度转换成坐标
  const [x, y] = projection(lightCenter);
  const lightPoint = createLightPillar(x, y, heightScaleFactor, depth);
  //这里创建文字坐标
  const label = createTextPoint(x, y, areaName, depth);
  return [lightPoint, label];
}

/**
 * @description 创建文字坐标
 * @param {*} x d3 - 经纬度转换后的x轴坐标
 * @param {*} y d3 - 经纬度转换后的y轴坐标
 * @param {*} areaName 地名
 */
function createTextPoint(x, y, areaName, depth) {
  let tag = document.createElement("div");
  tag.innerHTML = areaName;
  tag.style.color = "#fff";
  tag.style.pointerEvents = "auto";
  tag.style.position = "absolute";
  tag.addEventListener("mousedown", clickLabel, false); // 有时候PC端click事件不生效，不知道什么原因，就使用mousedown事件
  // tag.addEventListener("touchstart", clickLabel, false);
  let label = new CSS2DObject(tag);
  label.element.style.visibility = "visible";
  label.position.set(x, -y, depth);
  return label;
}

/**
 * @description 文字坐标点击
 * @param {*} e
 */
function clickLabel(e) {
  // 阻止事件冒泡
  e.stopPropagation();
  // 创建弹框元素
  const popup = document.createElement("div");
  popup.className = "map-popup";
  popup.innerHTML = `
    <div class="popup-content">
      <h3>${e.target.textContent}</h3>
      <p>这里是${e.target.textContent}的详细信息</p>
      <button class="close-btn">关闭</button>
    </div>
  `;
  // 设置弹框样式
  popup.style.position = "absolute";
  popup.style.left = `${e.clientX}px`;
  popup.style.top = `${e.clientY}px`;
  popup.style.zIndex = "1000";
  popup.style.backgroundColor = "rgba(0,0,0,0.8)";
  popup.style.padding = "15px";
  popup.style.borderRadius = "5px";
  popup.style.color = "white";

  // 添加关闭按钮事件
  const closeBtn = popup.querySelector(".close-btn");
  closeBtn.addEventListener("click", () => {
    document.body.removeChild(popup);
  });
  // 添加到页面
  document.body.appendChild(popup);
}

/**
 * @description 添加底部旋转背景
 */
function initFloor() {
  const geometry = new THREE.PlaneGeometry(80, 80);
  const material = new THREE.MeshPhongMaterial({
    color: 0xffffff,
    // map: texture,
    map: textureLoader.load(
      new URL("../assets/mapimg/地板背景.png", import.meta.url).href
    ),
    // emissive:0xffffff,
    // emissiveMap:Texture,
    transparent: true,
    opacity: 1,
    depthTest: true,
    // roughness:1,
    // metalness:0,
    depthWrite: false,
    // side: THREE.DoubleSide
  });
  let plane = new THREE.Mesh(geometry, material);
  plane.rotateX(-Math.PI / 2);
  scene.add(plane);

  // 旋转装饰圆环1
  let rotatingApertureTexture = textureLoader.load(
    new URL("../assets/mapimg/rotatingAperture.jpeg", import.meta.url).href
  );

  let rotatingApertureerial = new THREE.MeshBasicMaterial({
    map: rotatingApertureTexture,
    transparent: true,
    opacity: 1,
    depthTest: true,
    depthWrite: false,
  });
  let rotatingApertureGeometry = new THREE.PlaneGeometry(25, 25);
  rotatingApertureMesh = new THREE.Mesh(
    rotatingApertureGeometry,
    rotatingApertureerial
  );
  rotatingApertureMesh.rotateX(-Math.PI / 2);
  rotatingApertureMesh.position.y = 0.02;
  rotatingApertureMesh.scale.set(1.2, 1.2, 1.2);
  scene.add(rotatingApertureMesh);

  // 旋转装饰圆环2
  let rotatingPointTexture = textureLoader.load(
    new URL("../assets/mapimg/rotating-point2.jpeg", import.meta.url).href
  );
  let material2 = new THREE.MeshBasicMaterial({
    map: rotatingPointTexture,
    transparent: true,
    opacity: 1,
    depthTest: true,
    depthWrite: false,
  });

  rotatingPointMesh = new THREE.Mesh(rotatingApertureGeometry, material2);
  rotatingPointMesh.rotateX(-Math.PI / 2);
  rotatingPointMesh.position.y = 0.04;
  rotatingPointMesh.scale.set(1, 1, 1);
  scene.add(rotatingPointMesh);

  // 背景小圆点
  let circlePoint = textureLoader.load(
    new URL("../assets/mapimg/circle-point.jpeg", import.meta.url).href
  );
  let material3 = new THREE.MeshPhongMaterial({
    color: 0x00ffff,
    map: circlePoint,
    transparent: true,
    opacity: 1,
    depthWrite: false,
    // depthTest: false,
  });
  let plane3 = new THREE.PlaneGeometry(30, 30);
  let mesh3 = new THREE.Mesh(plane3, material3);
  mesh3.rotateX(-Math.PI / 2);
  mesh3.position.y = 0.06;
  scene.add(mesh3);
}

//加事件
function setClickFn() {
  raycaster = new THREE.Raycaster();
  mouse = new THREE.Vector2();
  let clickPosition;

  // 鼠标点击事件
  const onclick = (event) => {
    event.stopPropagation();
    let marginLeft = contant.offsetLeft;
    let marginTop = contant.offsetTop;
    // 如果该地图不是占满全屏需要减去margintop和marginleft
    let x = ((event.clientX - marginLeft) / contant.clientWidth) * 2 - 1;
    let y = -((event.clientY - marginTop) / contant.clientHeight) * 2 + 1;
    clickPosition = { x: x, y: y };
    raycaster.setFromCamera(clickPosition, camera);
    // 算出射线 与当场景相交的对象有那些
    const intersects = raycaster.intersectObjects(map.children, true);
    let clickObj = intersects.find(
      (item) => item.object.material && item.object.material.length === 2
    );
    // 点击
    if (clickObj && clickObj.object) {
      // 点击地图区域
      if (clickObj.object.type == "Mesh") {
        const mesh = clickObj.object;
        checkMesh.value = mesh;
      }
    }
  };
  window.addEventListener("mousedown", onclick, false);
}

// 设置最大旋转的角度
function setController() {
  const controls = new OrbitControls(camera, renderer.domElement);
  controls.maxPolarAngle = 2.5;
  controls.minPolarAngle = 1;
  controls.maxAzimuthAngle = 1;
  controls.minAzimuthAngle = -1;
  controls.addEventListener("change", () => {
    renderer.render(scene, camera);
  });
}

// 创建地图区块网格模型
function createMesh(data, depth) {
  // 创建一个多边形轮廓
  const shape = new THREE.Shape();

  // 循环多边形坐标数组  polygon是地图区块的经纬度坐标数组
  for (let i = 0; i < data.length; i++) {
    const [x, y] = projection(data[i]);
    if (i === 0) {
      shape.moveTo(x, -y);
    }
    shape.lineTo(x, -y);
  }

  // 拉伸参数
  const extrudeSettings = {
    depth: depth, // 拉伸度 （3D地图厚度）
    bevelEnabled: false, // 是否使用倒角
  };
  // 将多边形 拉伸扫描成型  //shape二维轮廓  //extrudeSettings拉伸参数
  const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);

  // 创建环境贴图
  let textureMap = textureLoader.load(
    new URL("../assets/mapimg/gz-map.jpeg", import.meta.url).href
  );
  let texturefxMap = textureLoader.load(
    new URL("../assets/mapimg/gz-map-fx.jpeg", import.meta.url).href
  );
  textureMap.wrapS = THREE.RepeatWrapping; //纹理水平方向的平铺方式
  textureMap.wrapT = THREE.RepeatWrapping; //纹理垂直方向的平铺方式
  textureMap.flipY = texturefxMap.flipY = false; // 如果设置为true，纹理在上传到GPU的时候会进行纵向的翻转。默认值为true。
  textureMap.rotation = texturefxMap.rotation = THREE.MathUtils.degToRad(45); //rotation纹理将围绕中心点旋转多少度
  const scale = 0.08;
  textureMap.repeat.set(scale, scale); //repeat决定纹理在表面的重复次数
  texturefxMap.repeat.set(scale, scale);
  textureMap.offset.set(0.5, 0.5); //offset贴图单次重复中的起始偏移量
  texturefxMap.offset.set(0.5, 0.5);

  // 创建高光材质
  const material = new THREE.MeshPhongMaterial({
    map: textureMap, //颜色贴图
    normalMap: texturefxMap, //用于创建法线贴图的纹理
    // normalScale: new THREE.Vector2(12.2, 2.2),//法线贴图对材质的影响程度
    color: "#7bc6c2",
    combine: THREE.MultiplyOperation, //如何将表面颜色的结果与环境贴图
    transparent: true,
    opacity: 1,
  });

  // 创建基础网格材质
  const material1 = new THREE.MeshBasicMaterial({
    color: 0x123024,
    transparent: true,
    opacity: 0.9,
  });

  // 多边形添加材质
  const mesh = new THREE.Mesh(geometry, [
    material, //表面材质
    material1, //侧面材质
  ]);
  mesh.name = "地图区域";
  return mesh;
}

// 创建地图区块边界线
function createLine(data, depth) {
  // 线材质对象 创建地图边界线 白色
  const lineMaterial = new THREE.LineBasicMaterial({
    color: "white",
  });
  // 创建一个空的几何体对象
  const lineGeometry = new THREE.BufferGeometry();
  const pointsArray = new Array();
  // 循环多边形坐标数组  polygon是地图区块的经纬度坐标数组
  for (let i = 0; i < data.length; i++) {
    const [x, y] = projection(data[i]);
    // 三维向量对象  用于绘制边界线
    pointsArray.push(new THREE.Vector3(x, -y, 0));
  }
  // setFromPoints方法从pointsArray中提取数据改变几何体的顶点属性vertices
  // 如果几何体是BufferGeometry，setFromPoints方法改变的是.attributes.position属性
  lineGeometry.setFromPoints(pointsArray);

  const line = new THREE.Line(lineGeometry, lineMaterial);
  line.position.z += depth + 0.001;
  line.name = "地图边界线";
  return line;
}

// 设置地图中心位置
function setCenter(map) {
  map.rotation.x = -Math.PI / 2;
  // 把地图放到"盒子"里面
  const box = new THREE.Box3().setFromObject(map);
  // 获取盒子中心点
  const center = box.getCenter(new THREE.Vector3());
  const offset = [0, 0];
  // 设置地图偏移  放到中心位置
  map.position.x = map.position.x - center.x - offset[0];
  map.position.z = map.position.z - center.z - offset[1];
}

// 点击交互 改变选中区域样式
function changeMesh(newVal) {
  // 变量地图模块
  map.children.map((item) => {
    if (item.type == "Object3D") {
      // 找到点击区域的父级元素
      if (item.uuid == newVal.parent.uuid) {
        // 改变Z坐标位置
        item.position.z = 0.5;
        // 遍历子元素
        item.children.map((element) => {
          if (element.type == "Mesh") {
            // 修改所有的区域颜色  黄色
            element.material[0].color.set(0xfeea37);
            // 侧边 深黄色
            element.material[1].color.set(0xa5960b);
          }
        });
      } else {
        // 还原位置
        item.position.z = 0;
        item.children.map((element) => {
          if (element.type == "Mesh") {
            // 还原区域颜色
            element.material[0].color.set(0x7bc6c2);
            element.material[1].color.set(0x123024);
          }
        });
      }
    }
    return item;
  });
}

function animate() {
  // 背景圆环 转动
  if (rotatingApertureMesh) {
    rotatingApertureMesh.rotation.z += 0.0005;
  }
  if (rotatingPointMesh) {
    rotatingPointMesh.rotation.z -= 0.0005;
  }
  // 圆柱底部 波纹
  if (WaveMeshArr.length) {
    WaveMeshArr.forEach(function (mesh) {
      mesh._s += 0.007;
      mesh.scale.set(
        mesh.size * mesh._s,
        mesh.size * mesh._s,
        mesh.size * mesh._s
      );
      if (mesh._s <= 1.5) {
        //mesh._s=1，透明度=0 mesh._s=1.5，透明度=1
        mesh.material.opacity = (mesh._s - 1) * 2;
      } else if (mesh._s > 1.5 && mesh._s <= 2) {
        //mesh._s=1.5，透明度=1 mesh._s=2，透明度=0
        mesh.material.opacity = 1 - (mesh._s - 1.5) * 2;
      } else {
        mesh._s = 1.0;
      }
    });
  }

  window.requestAnimationFrame(animate);
  // 通过摄像机和鼠标位置更新射线
  raycaster.setFromCamera(mouse, camera);
  labelRenderer.render(scene, camera);

  renderer.render(scene, camera);
}
</script>

<style>
.center-map-box {
  width: 100%;
  height: 100%;
  position: relative;
  font-size: 14px;
}

.map-popup {
  min-width: 200px;
  max-width: 300px;
}

.popup-content h3 {
  margin-top: 0;
  color: #00ffff;
}

.close-btn {
  background: #00ffff;
  border: none;
  padding: 5px 10px;
  border-radius: 3px;
  cursor: pointer;
  margin-top: 10px;
}
</style>